package com.gzkemays.auth.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
  public static final String RESOURCE_ID = "resource-1";

  @Resource private PasswordEncoder passwordEncoder;

  // redis 连接工厂
  @Resource private RedisConnectionFactory redisConnectionFactory;
  // 数据库源
  @Resource private DataSource dataSource;
  // Token 增强器
  @Resource private CustomTokenEnhancer customTokenEnhancer;
  // 授权管理器
  // 如果出现无法注入的问题，先在 WebSecurityConfigurerAdapter 中重写 return 方法并注入 Bean。
  @Resource private AuthenticationManager authenticationManager;

  /**
   * 配置授权服务器的安全性，令牌端点的安全约束
   *
   * @param security
   * @throws Exception
   */
  @Override
  public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
    security
        // 开启 /oauth/check_token
        .tokenKeyAccess("permitAll()")
        // 开启 /oauth/token_key
        .checkTokenAccess("isAuthenticated()")
        // 允许表单认证
        // 如果配置，且url中有client_id和client_secret的，则走 ClientCredentialsTokenEndpointFilter
        // 如果没有配置，但是url中没有client_id和client_secret的，走basic认证保护
        .allowFormAuthenticationForClients();
  }

  /**
   * 配置服务器，可在内存中和数据库中
   *
   * @param clients
   * @throws Exception
   */
  @Override
  public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    // 自定义
    //    clients
    //        .inMemory()
    //        .withClient("client_1")
    //        .resourceIds(RESOURCE_ID)
    //        .authorizedGrantTypes("authorization_code", "refresh_token")
    //        .scopes("read")
    //        .authorities("client")
    //        .secret(passwordEncoder.encode("123456"))
    //        // 必须添加，会和请求时重定向地址匹配
    //
    // .redirectUris("http://localhost:8001/service1/login","http://localhost:8002/service2/login");

    // 从数据库中查询
    clients.withClientDetails(clientDetails(dataSource));

    // 自动批准，在登录成功后不会跳到批准页面，让资源所有者批准
    // .autoApprove(true);
  }

  @Override
  public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    // token增强配置
    //    TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
    //    tokenEnhancerChain.setTokenEnhancers(
    //        Arrays.asList(customTokenEnhancer, jwtAccessTokenConverter()));

    endpoints.tokenStore(tokenStore()).authenticationManager(authenticationManager);
    endpoints.tokenServices(tokenServices()).accessTokenConverter(jwtAccessTokenConverter());
    endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
    //    endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);

    //    endpoints
    //        // 令牌存在redis
    //        .tokenStore(tokenStore())
    //        // 密码授权方式时需要
    //        .authenticationManager(authenticationManager)
    //        // /oauth/token 运行get和post
    //        .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
    //        .tokenEnhancer(tokenEnhancerChain);
  }

  /**
   * 配置redis，使用redis存token
   *
   * @return
   */
  @Bean
  public TokenStore tokenStore() {
    return new RedisTokenStore(redisConnectionFactory);
  }

  @Primary
  @Bean
  public DefaultTokenServices tokenServices() {
    DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
    defaultTokenServices.setTokenStore(tokenStore());
    // 维持刷新token
    defaultTokenServices.setSupportRefreshToken(true);
    // 通过TokenEnhancerChain增强器链将jwtAccessTokenConverter(转换成jwt)和jwtTokenEnhancer(往里面加内容加信息)连起来
    TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
    List<TokenEnhancer> enhancerList = new ArrayList<>();
    enhancerList.add(customTokenEnhancer);
    enhancerList.add(jwtAccessTokenConverter());
    enhancerChain.setTokenEnhancers(enhancerList);
    defaultTokenServices.setTokenEnhancer(enhancerChain);
    return defaultTokenServices;
  }

  /**
   * 获取客户端详细信息服务，JDBC实现
   *
   * @return
   */
  @Bean
  public ClientDetailsService clientDetails(DataSource ds) {
    return new JdbcClientDetailsService(ds);
  }

  /**
   * 用来生成token的转换器
   *
   * @return
   */
  @Bean
  public JwtAccessTokenConverter jwtAccessTokenConverter() {
    JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
    // 对称加密，设置签名，使用下面这个值作为密钥
    jwtAccessTokenConverter.setSigningKey("oauth");
    return jwtAccessTokenConverter;
  }
}
